import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_demo/pages/animation/implicitly_animated_widget_page.dart';
import 'package:flutter_demo/pages/page_with_title.dart';

import '../../widgets/app_widgets.dart';
import 'animation_page.dart';

class AnimatedWidgetPage extends PageWithTitle {
  const AnimatedWidgetPage() : super("动画组件(AnimatedWidget)");

  @override
  List<Widget> buildContentWidgets(BuildContext context) {
    return const [
      PrimaryTitle("简介"),
      BodyText("在Flutter中，实现组件动画效果可以通过各类Transition组件来包含要执行动画的"
          "组件来实现，他们以Animation来驱动，内部对canvas进行一些变幻操作来实现child的动画"
          "效果"),
      LabelText("- 我们可以通过嵌套多种动画组件来实现复合的动画效果"),
      LabelText("- AnimatedWidget还需要外部提供Animation并管理动画生命周期，sdk还封装了"
          "隐式动画组件，在构建参数变化时内部自动以动画效果过渡，对调用者来说只是修改了属性就"
          "产生了动画效果，相当方便，参考："),
      PageLink(ImplicitlyAnimatedWidgetPage()),

      AppSpacer.large,
      UlItem("AnimatedWidget"),
      BodyText("带动画组件，构造时传入Animation并在内部订阅，在value更新时通过setState触发组件重建，实现"
          "动画效果更新，这是个抽象类，常见的动画效果都已经有对应的子类实现（Transition）"),

      AppSpacer.large,
      PrimaryTitle("常见的动画：透明度、位移、旋转、缩放"),
      _FadeExample(),
      OlItem(1, "FadeTransition"),
      BodyText("透明度动画，内部通过覆写renderObject.updateCompositedLayer，对合成图层设"
          "置透明度，这样child渲染时就会拿到已经修改过透明度的画布"),
      LabelText("- opacity，透明度动画驱动"),

      AppSpacer.large,
      _SlideExample(),
      OlItem(2, "SlideTransition"),
      BodyText("滑动动画，类似位移动画，它以相对自身尺寸的比值进行位移，内部是通过FractionalTranslation实现的"),
      LabelText("- position，位置动画驱动"),
      BodyText("按像素进行位移动画，可以通过PositionedTransition、"
          "RelativePositionedTransition来实现，只要左与右 上与下是相同的偏移量即可实现位"
          "移动画，但大部分情况是按照自身尺寸百分比来实现动画的场景"),

      AppSpacer.large,
      _PositionedExample(),
      OlItem(3, "PositionedTransition"),
      BodyText("定位动画，以动画的形式将组件左上右下四条边分别往相对位置移动，实现动画效果"),
      NoticeText("注意：它必须作为子组件放置于Stack组件内，并且与Stack组件之间不能有其它可渲染组件"),
      LabelText("- rect，一个矩形的动画对象，组件的四条边将按照相对矩形计算实时位置，"
          "通常用RelativeRectTween来构造，它可以指定四条边开始位置与结束位置实现过渡动画"),
      LabelText("- RelativeRect，相对矩形，表示一个矩形相对于另一个矩形（容器）的位置信息，"
          "我们知道一般的矩形表示四边相对于原点（xy）的位置，那如何表示相对于一个容器（ltrb）的"
          "位置呢？目前的表达方式被定义为矩形四条边相对于容器四条边的内陷程度，类似于Java Rect"
          ".inset或Flutter/Kotlin Rect.deflate。"),
      BodyText("如果相对矩形用不惯，可以使用RelativePositionedTransition来实现，它使用"
          "偏移量来定义四条边的位置"),

      AppSpacer.large,
      _RelativePositionedExample(),
      OlItem(4, "RelativePositionedTransition"),
      BodyText("相对定位动画，它使用Rect来表示组件四条边的实时偏移量，较好理解"),
      NoticeText("注意：它一样需要放置在Stack组件内"),
      LabelText("- rect，它以Rect动画来实时更新组件四条边的偏移量，通过RectTween来构建"),
      LabelText("- size，子组件尺寸，这个尺寸是相对于容器的，子组件以容器左上角为起点，size"
          "宽高表示为距离容器的右下边距"),

      AppSpacer.large,
      _RotationExample(),
      AppSpacer.large,
      OlItem(5, "RotationTransition"),
      BodyText("旋转动画，基于MatrixTransition实现绕Z轴旋转"),
      LabelText("- turns 旋转动画驱动，数值为1.0表示旋转2pi，即360度"),
      LabelText("- alignment 按尺寸比例设置动画中心"),
      LabelText("- filterQuality 过滤器质量，一般不用设置，如果发现旋转时过于模糊可以尝试"
          "提高质量，内部会采用不同的算法进行采样，性能和质量有所差异"),

      AppSpacer.large,
      _ScaleExample(),
      AppSpacer.large,
      OlItem(6, "ScaleTransition"),
      BodyText("缩放动画，基于MatrixTransition实现绕x、y坐标数值缩放"),
      LabelText("- scale 缩放动画驱动"),
      LabelText("- alignment 动画中心"),
      LabelText("- filterQuality 过滤器质量"),

      AppSpacer.large,
      OlItem(7, "MatrixTransition"),
      BodyText("矩阵动画，内部用Transform包裹child实现变换效果，因为常见的变换类型动画都有"
          "各自的实现，一般用不到这个"),
      LabelText("- animation 动画驱动"),
      LabelText("- onTransform 通过该回调，根据当前动画进度值返回一个矩阵应用于子组件"),
      LabelText("- alignment 动画中心"),
      LabelText("- filterQuality 过滤器质量"),

      AppSpacer.large,
      _SizeExample(),
      OlItem(8, "SizeTransition"),
      BodyText("尺寸动画，指定一个轴（横向或者纵向），对该轴的数值按动画进度进行缩放，交叉轴数"
          "值不变（但可以指定初始值）"),
      NoticeText("注意：子组件需要设置尺寸，而父组件不能固定本组件的尺寸（例如不能使用"
          "Container），它只有在可变大小的容器中才能通过修改尺寸实现动画"),
      LabelText("- axis 指定动画轴"),
      LabelText("- sizeFactor 该轴的动画驱动对象"),
      LabelText("- axisAlignment 轴对齐位置，用于缩放中心（默认0表示中心，-1表示开始位置，"
          "1表示结束位置）"),
      LabelText("- fixedCrossAxisSizeFactor 固定的交叉轴缩放比例"),

      AppSpacer.large,
      PrimaryTitle("此外，Flutter还提供了一些特殊的样式动画"),
      _AlignExample(),
      OlItem(9, "AlignTransition"),
      BodyText("对齐动画，可以实现在切换不同对齐方式时，以动画效果过渡，例如这里的左对齐切换到右对齐"),
      LabelText("- alignment 对齐动画驱动，通过AlignmentTween可以快速构建两个对齐效果之间的驱动动画"),

      AppSpacer.large,
      _DecoratedBoxExample(),
      OlItem(10, "DecoratedBoxTransition"),
      BodyText("装饰盒动画，可以实现在不同的背景色、圆角的装饰间切换时以动画效果过渡"),
      LabelText("- decoration 装饰动画驱动，通过DecorationTwee来创建两个装饰之间的过渡动画"),

      AppSpacer.large,
      _DefaultTextStyleExample(),
      OlItem(10, "DefaultTextStyleTransition"),
      BodyText("默认文本样式动画，可以实现文本样式切换时以动画过渡，支持TextStyle所有属性"),
      LabelText("- style 文本样式动画驱动，通过TextStyleTwee来创建两个样式之间的过渡动画"
          "(我发现字体大小改变时，动画会非常抖，怎么设置它的基线在容器中固定位置呢)"),

      AppSpacer.large,
      PrimaryTitle("自定义动画"),
      _AnimatedBuilderExample(),
      OlItem(11, "AnimatedBuilder"),
      BodyText("动画构建器，结合AnimationController可以快速实现动画功能，与Transition内"
          "部封装动画实现过程不同，AnimatedBuilder将动画的实现通过builder暴露出来，由开发者"
          "自己在每一次build时根据动画进度构建实时状态的组件实现动画效果，理论上可以实现上方所"
          "有动画效果"),
      NoticeText("示例中的动画由两种方式实现，虽然看起来效果一样，但其实有所差异"),
      NoticeText("- Transform实现的变换效果，它因为是对画布进行变换，动画更新时只做了重绘，"
          "若小方块可以点击，则它的点击位置依然在圆心位置，优势是计算量较少性能较高"),
      NoticeText("- Positioned是通过重新布局方式修改了小方块的实际位置，若小方块可以点击，"
          "点击的位置是跟随小方块变化的，大多数动画不需要与用户交互，若仅靠重绘来实现会有比较好"
          "的性能优势"),
    ];
  }
}

class _FadeExample extends StatelessWidget {
  const _FadeExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return FadeTransition(
          opacity: animation,
          child: Container(
            height: 40,
            color: Theme.of(context).colorScheme.primary,
          ));
    });
  }
}

class _SlideExample extends StatelessWidget {
  const _SlideExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return SlideTransition(
          position: Tween(
            begin: Offset.zero,
            end: const Offset(-1, 0),
          ).animate(animation),
          child: Container(
            height: 40,
            color: Theme.of(context).colorScheme.primary,
          ));
    });
  }
}

class _PositionedExample extends StatelessWidget {
  const _PositionedExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return SizedBox(
        width: 40,
        height: 40,
        child: Stack(
          children: [
            PositionedTransition(
              rect: RelativeRectTween(
                begin: const RelativeRect.fromLTRB(0, 0, 0, 0),
                end: const RelativeRect.fromLTRB(0, 20, 360, 20),
              ).animate(animation),
              child: ColoredBox(
                color: Theme.of(context).primaryColor,
              ),
            )
          ],
        ),
      );
    });
  }
}

class _RelativePositionedExample extends StatelessWidget {
  const _RelativePositionedExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return SizedBox(
        width: 40,
        height: 40,
        child: Stack(
          children: [
            RelativePositionedTransition(
              rect: RectTween(
                begin: const Rect.fromLTRB(0, 0, 0, 0),
                end: const Rect.fromLTRB(0, 20, -260, -20),
              ).animate(animation),
              size: const Size(100, 0),
              child: ColoredBox(
                color: Theme.of(context).primaryColor,
              ),
            )
          ],
        ),
      );
    });
  }
}

class _RotationExample extends StatelessWidget {
  const _RotationExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return Center(
        child: SizedBox(
          width: 40,
          height: 40,
          child: RotationTransition(
            turns: animation,
            alignment: Alignment.center,
            child: ColoredBox(
              color: Theme.of(context).colorScheme.primary,
            ),
          ),
        ),
      );
    });
  }
}

class _ScaleExample extends StatelessWidget {
  const _ScaleExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return Center(
        child: SizedBox(
          width: 40,
          height: 40,
          child: ScaleTransition(
            scale: animation,
            alignment: Alignment.center,
            child: ColoredBox(
              color: Theme.of(context).colorScheme.primary,
            ),
          ),
        ),
      );
    });
  }
}

class _SizeExample extends StatelessWidget {
  const _SizeExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return Center(
        child: SizeTransition(
          axis: Axis.horizontal,
          sizeFactor: animation,
          axisAlignment: 0,
          fixedCrossAxisSizeFactor: 1,
          child: Container(
            width: 40,
            height: 40,
            color: Theme.of(context).colorScheme.primary,
          ),
        ),
      );
    });
  }
}

class _AlignExample extends StatelessWidget {
  const _AlignExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return AlignTransition(
        alignment: AlignmentTween(
          begin: Alignment.centerLeft,
          end: Alignment.centerRight,
        ).animate(animation),
        child: Container(
          width: 40,
          height: 40,
          color: Theme.of(context).colorScheme.primary,
        ),
      );
    });
  }
}

class _DecoratedBoxExample extends StatelessWidget {
  const _DecoratedBoxExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return DecoratedBoxTransition(
        decoration: DecorationTween(
          begin: BoxDecoration(
            color: Theme.of(context).colorScheme.primary,
            borderRadius: BorderRadius.zero,
          ),
          end: BoxDecoration(
            color: Theme.of(context).colorScheme.tertiary,
            borderRadius: const BorderRadius.all(
              Radius.circular(20),
            ),
          ),
        ).animate(animation),
        child: Container(
            width: double.infinity,
            height: 40,
            alignment: Alignment.center,
            child: const Text("test decoration")),
      );
    });
  }
}

class _DefaultTextStyleExample extends StatelessWidget {
  const _DefaultTextStyleExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return DefaultTextStyleTransition(
        style: TextStyleTween(
          begin: Theme.of(context).textTheme.bodySmall,
          end: Theme.of(context).textTheme.bodyLarge,
        ).animate(animation),
        child: Container(
            width: double.infinity,
            height: 40,
            alignment: Alignment.center,
            child: const Text("test dynamic text style")),
      );
    });
  }
}

class _AnimatedBuilderExample extends StatelessWidget {
  const _AnimatedBuilderExample();

  @override
  Widget build(BuildContext context) {
    return ControllerAnimation((context, animation) {
      return AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          //每次动画更新，都会重新build
          final radians = animation.value * pi * 2; //1次动画循环旋转360，也就是2pi
          final dx = sin(radians) * 40; //三角函数求得x偏移量
          //三角函数求得y偏移量，因为屏幕坐标系与笛卡尔坐标系y轴方向相反，这里要转成负值
          final dy = cos(radians) * -40;
          return Row(
            children: [
              SizedBox(
                width: 100,
                height: 100,
                child: Stack(
                  children: [
                    Positioned(//在容器中用positioned来偏移组件
                      //-10表示将20x20的方块居中到原点，+50表示将方块居中到SizedBox中心，再加
                      // 上三角函数根据角度计算出偏移量，模拟时钟顺时针旋转
                      left: -10 + 50 + dx,
                      top: -10 + 50 + dy,
                      child: Container(
                        width: 20,
                        height: 20,
                        color: Theme.of(context).colorScheme.primary,
                      ),
                    )
                  ],
                ),
              ),
              SizedBox(
                width: 100,
                height: 100,
                child: Center(
                  //在不需要交互的组件中实现动画效果，使用Transform来实现变换更好，因为它只
                  // 需要更新绘制而不需要重新布局，性能相对较高
                  child: Transform.translate(
                    offset: Offset(dx,dy),
                    child: SizedBox(
                      width: 20,
                      height: 20,
                      child: ColoredBox(
                        color: Theme.of(context).colorScheme.primary,
                      ),
                    ),
                  ),
                ),
              ),
            ],
          );
        },
      );
    });
  }
}
